/*
 * Copyright 2017 Google
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#import "FOrderByTests.h"

@interface FOrderByTests ()
@end

@implementation FOrderByTests


- (void) testCanDefineAndUseAnIndex {
    __block FIRDatabaseReference *ref = [FTestHelpers getRandomNode];

    NSArray *users = @[
                    @{@"name": @"Andrew", @"nuggets": @35},
                    @{@"name": @"Rob", @"nuggets": @40},
                    @{@"name": @"Greg", @"nuggets": @38}
    ];

    __block int setCount = 0;
    [users enumerateObjectsUsingBlock:^(NSDictionary *user, NSUInteger idx, BOOL *stop) {
        [[ref childByAutoId] setValue:user withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
            setCount++;
        }];
    }];

    [self waitUntil:^BOOL{
        return setCount == users.count;
    }];

    __block NSMutableArray *byNuggets = [[NSMutableArray alloc] init];
    [[ref queryOrderedByChild:@"nuggets"] observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
        NSDictionary *user = snapshot.value;
        [byNuggets addObject:user[@"name"]];
    }];

    [self waitUntil:^BOOL{
        return byNuggets.count == users.count;
    }];

    NSArray *expected = @[@"Andrew", @"Greg", @"Rob"];
    XCTAssertEqualObjects(byNuggets, expected, @"Correct by-nugget ordering.");
}

- (void) testCanDefineAndUseDeepIndex {
    __block FIRDatabaseReference *ref = [FTestHelpers getRandomNode];

    NSArray *users = @[
                       @{@"name": @"Andrew", @"deep": @{@"nuggets": @35}},
                       @{@"name": @"Rob", @"deep": @{@"nuggets": @40}},
                       @{@"name": @"Greg", @"deep": @{@"nuggets": @38}}
                       ];

    __block int setCount = 0;
    [users enumerateObjectsUsingBlock:^(NSDictionary *user, NSUInteger idx, BOOL *stop) {
        [[ref childByAutoId] setValue:user withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
            setCount++;
        }];
    }];

    [self waitUntil:^BOOL{
        return setCount == users.count;
    }];

    __block NSMutableArray *byNuggets = [[NSMutableArray alloc] init];
    [[ref queryOrderedByChild:@"deep/nuggets"] observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
        NSDictionary *user = snapshot.value;
        [byNuggets addObject:user[@"name"]];
    }];

    [self waitUntil:^BOOL{
        return byNuggets.count == users.count;
    }];

    NSArray *expected = @[@"Andrew", @"Greg", @"Rob"];
    XCTAssertEqualObjects(byNuggets, expected, @"Correct by-nugget ordering.");
}

- (void) testCanUsaAFallbackThenDefineTheSpecifiedIndex {
    FTupleFirebase *tuple = [FTestHelpers getRandomNodePair];
    FIRDatabaseReference *reader = tuple.one, *writer = tuple.two;

    NSDictionary *foo1 = @{
            @"a" : @{@"order" : @2, @"foo" : @1},
            @"b" : @{@"order" : @0},
            @"c" : @{@"order" : @1, @"foo" : @NO},
            @"d" : @{@"order" : @3, @"foo" : @"hello"}
    };

    NSDictionary *foo_e = @{@"order": @1.5, @"foo": @YES};
    NSDictionary *foo_f = @{@"order": @4, @"foo": @{@"bar": @"baz"}};

    [self waitForCompletionOf:writer setValue:foo1];

    NSMutableArray *snaps = [[NSMutableArray alloc] init];
    [[[reader queryOrderedByChild:@"order"] queryLimitedToLast:2] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
        [snaps addObject:snapshot.value];
    }];
    WAIT_FOR(snaps.count == 1);

    NSDictionary *expected = @{
            @"d": @{@"order": @3, @"foo": @"hello"},
            @"a": @{@"order": @2, @"foo": @1}
    };
    XCTAssertEqualObjects(snaps[0], expected, @"Got correct result");


    [self waitForCompletionOf:[writer child:@"e"] setValue:foo_e];

    [self waitForRoundTrip:reader];
    NSLog(@"snaps: %@", snaps);
    NSLog(@"snaps.count: %ld", (unsigned long) snaps.count);
    XCTAssertEqual(snaps.count, (NSUInteger)1, @"Should still have one event.");

    [self waitForCompletionOf:[writer child:@"f"] setValue:foo_f];

    [self waitForRoundTrip:reader];
    XCTAssertEqual(snaps.count, (NSUInteger)2, @"Should have gotten another event.");
    expected = @{
            @"f": foo_f,
            @"d": @{@"order": @3, @"foo": @"hello"}
    };
    XCTAssertEqualObjects(snaps[1], expected, @"Correct event.");
}

- (void) testSnapshotsAreIteratedInOrder {
    FIRDatabaseReference *ref = [FTestHelpers getRandomNode];

    NSDictionary *initial = @{
            @"alex": @{@"nuggets": @60},
            @"rob": @{@"nuggets": @56},
            @"vassili": @{@"nuggets": @55.5},
            @"tony": @{@"nuggets": @52},
            @"greg": @{@"nuggets": @52}
    };

    NSArray *expectedOrder = @[@"greg", @"tony", @"vassili", @"rob", @"alex"];
    NSArray *expectedPrevNames = @[[NSNull null], @"greg", @"tony", @"vassili", @"rob"];

    NSMutableArray *valueOrder = [[NSMutableArray alloc] init];
    NSMutableArray *addedOrder = [[NSMutableArray alloc] init];
    NSMutableArray *addedPrevNames = [[NSMutableArray alloc] init];

    FIRDatabaseQuery *orderedRef = [ref queryOrderedByChild:@"nuggets"];

    [orderedRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
        for (FIRDataSnapshot *child in snapshot.children) {
            [valueOrder addObject:child.key];
        }
    }];

    [orderedRef observeEventType:FIRDataEventTypeChildAdded andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *prevName) {
        [addedOrder addObject:snapshot.key];
        [addedPrevNames addObject:prevName ? prevName : [NSNull null]];
    }];

    [ref setValue:initial];
    WAIT_FOR(addedOrder.count == expectedOrder.count && valueOrder.count == expectedOrder.count);

    XCTAssertEqualObjects(addedOrder, expectedOrder, @"child_added events in correct order.");
    XCTAssertEqualObjects(addedPrevNames, expectedPrevNames, @"Got correct prevnames for child_added events.");
    XCTAssertEqualObjects(valueOrder, expectedOrder, @"enumerated snapshot children in correct order.");
}

- (void) testSnapshotsAreIteratedInOrderForValueIndex {
    FIRDatabaseReference *ref = [FTestHelpers getRandomNode];

    NSDictionary *initial = @{
                              @"alex": @60,
                              @"rob": @56,
                              @"vassili": @55.5,
                              @"tony": @52,
                              @"greg": @52
                              };

    NSArray *expectedOrder = @[@"greg", @"tony", @"vassili", @"rob", @"alex"];
    NSArray *expectedPrevNames = @[[NSNull null], @"greg", @"tony", @"vassili", @"rob"];

    NSMutableArray *valueOrder = [[NSMutableArray alloc] init];
    NSMutableArray *addedOrder = [[NSMutableArray alloc] init];
    NSMutableArray *addedPrevNames = [[NSMutableArray alloc] init];

    FIRDatabaseQuery *orderedRef = [ref queryOrderedByValue];

    [orderedRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
        for (FIRDataSnapshot *child in snapshot.children) {
            [valueOrder addObject:child.key];
        }
    }];

    [orderedRef observeEventType:FIRDataEventTypeChildAdded andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *prevName) {
        [addedOrder addObject:snapshot.key];
        [addedPrevNames addObject:prevName ? prevName : [NSNull null]];
    }];

    [ref setValue:initial];
    WAIT_FOR(addedOrder.count == expectedOrder.count && valueOrder.count == expectedOrder.count);

    XCTAssertEqualObjects(addedOrder, expectedOrder, @"child_added events in correct order.");
    XCTAssertEqualObjects(addedPrevNames, expectedPrevNames, @"Got correct prevnames for child_added events.");
    XCTAssertEqualObjects(valueOrder, expectedOrder, @"enumerated snapshot children in correct order.");
}

- (void) testFiresChildMovedEvents {
    FIRDatabaseReference *ref = [FTestHelpers getRandomNode];

    NSDictionary *initial = @{
            @"alex": @{@"nuggets": @60},
            @"rob": @{@"nuggets": @56},
            @"vassili": @{@"nuggets": @55.5},
            @"tony": @{@"nuggets": @52},
            @"greg": @{@"nuggets": @52}
    };

    FIRDatabaseQuery *orderedRef = [ref queryOrderedByChild:@"nuggets"];

    __block BOOL moved = NO;
    [orderedRef observeEventType:FIRDataEventTypeChildMoved andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *prevName) {
        moved = YES;
        XCTAssertEqualObjects(snapshot.key, @"greg", @"");
        XCTAssertEqualObjects(prevName, @"rob", @"");
        XCTAssertEqualObjects(snapshot.value, @{@"nuggets" : @57}, @"");
    }];

    [ref setValue:initial];
    [[ref child:@"greg/nuggets"] setValue:@57];
    WAIT_FOR(moved);
}

- (void) testDefineMultipleIndexesAtALocation {
    FTupleFirebase *tuple = [FTestHelpers getRandomNodePair];
    FIRDatabaseReference *reader = tuple.one, *writer = tuple.two;

    NSDictionary *foo1 = @{
            @"a" : @{@"order" : @2, @"foo" : @2},
            @"b" : @{@"order" : @0},
            @"c" : @{@"order" : @1, @"foo" : @NO},
            @"d" : @{@"order" : @3, @"foo" : @"hello"}
    };


    [self waitForCompletionOf:writer setValue:foo1];

    FIRDatabaseQuery *fooOrder = [reader queryOrderedByChild:@"foo"];
    FIRDatabaseQuery *orderOrder = [reader queryOrderedByChild:@"order"];
    NSMutableArray *fooSnaps = [[NSMutableArray alloc] init];
    NSMutableArray *orderSnaps = [[NSMutableArray alloc] init];

    [[[fooOrder queryStartingAtValue:nil] queryEndingAtValue:@1] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
        [fooSnaps addObject:snapshot.value];
    }];

    [[orderOrder queryLimitedToLast:2] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
        [orderSnaps addObject:snapshot.value];
    }];

    WAIT_FOR(fooSnaps.count == 1 && orderSnaps.count == 1);

    NSDictionary *expected = @{
            @"b": @{@"order": @0},
            @"c": @{@"order": @1, @"foo": @NO}
    };
    XCTAssertEqualObjects(fooSnaps[0], expected, @"");

    expected = @{
            @"d": @{@"order": @3, @"foo": @"hello"},
            @"a": @{@"order": @2, @"foo": @2},
    };
    XCTAssertEqualObjects(orderSnaps[0], expected, @"");

    [[writer child:@"a"] setValue:@{
            @"order": @-1, @"foo": @1
    }];

    WAIT_FOR(fooSnaps.count == 2 && orderSnaps.count == 2);

    expected = @{
            @"a": @{@"order": @-1, @"foo": @1 },
            @"b": @{@"order": @0},
            @"c": @{@"order": @1, @"foo": @NO}
    };
    XCTAssertEqualObjects(fooSnaps[1], expected, @"");

    expected = @{
            @"d": @{@"order": @3, @"foo": @"hello"},
            @"c": @{@"order": @1, @"foo": @NO}
    };
    XCTAssertEqualObjects(orderSnaps[1], expected, @"");
}

- (void) testCallbackRemovalWorks {
    FIRDatabaseReference *ref = [FTestHelpers getRandomNode];

    __block int reads = 0;
    FIRDatabaseHandle fooHandle, bazHandle;
    fooHandle = [[ref queryOrderedByChild:@"foo"] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
        reads++;
    }];

    [[ref queryOrderedByChild:@"bar"] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
        reads++;
    }];

    bazHandle = [[ref queryOrderedByChild:@"baz"] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
        reads++;
    }];

    [ref observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
        reads++;
    }];

    [self waitForCompletionOf:ref setValue:@1];

    XCTAssertEqual(reads, 4, @"");

    [ref removeObserverWithHandle:fooHandle];
    [self waitForCompletionOf:ref setValue:@2];
    XCTAssertEqual(reads, 7, @"");

    // should be a no-op, resulting in 3 more reads.
    [[ref queryOrderedByChild:@"foo"] removeObserverWithHandle:bazHandle];
    [self waitForCompletionOf:ref setValue:@3];
    XCTAssertEqual(reads, 10, @"");

    [[ref queryOrderedByChild:@"bar"] removeAllObservers];
    [self waitForCompletionOf:ref setValue:@4];
    XCTAssertEqual(reads, 12, @"");

    // Now, remove everything.
    [ref removeAllObservers];
    [self waitForCompletionOf:ref setValue:@5];
    XCTAssertEqual(reads, 12, @"");
}

- (void) testChildAddedEventsAreInTheCorrectOrder {
    FIRDatabaseReference *ref = [FTestHelpers getRandomNode];

    NSDictionary *initial = @{
            @"a": @{@"value": @5},
            @"c": @{@"value": @3}
    };

    NSMutableArray *added = [[NSMutableArray alloc] init];
    [[ref queryOrderedByChild:@"value"] observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
        [added addObject:snapshot.key];
    }];
    [ref setValue:initial];

    WAIT_FOR(added.count == 2);
    NSArray *expected = @[@"c", @"a"];
    XCTAssertEqualObjects(added, expected, @"");

    [ref updateChildValues:@{
            @"b": @{@"value": @4},
            @"d": @{@"value": @2}
    }];

    WAIT_FOR(added.count == 4);
    expected = @[@"c", @"a", @"d", @"b"];
    XCTAssertEqualObjects(added, expected, @"");
}

- (void) testCanUseKeyIndex {
    FIRDatabaseReference *ref = [FTestHelpers getRandomNode];

    NSDictionary *data = @{
            @"a": @{ @".priority": @10, @".value": @"a" },
            @"b": @{ @".priority": @5, @".value": @"b" },
            @"c": @{ @".priority": @20, @".value": @"c" },
            @"d": @{ @".priority": @7, @".value": @"d" },
            @"e": @{ @".priority": @30, @".value": @"e" },
            @"f": @{ @".priority": @8, @".value": @"f" }
    };

    [self waitForCompletionOf:ref setValue:data];

    __block BOOL valueDone = NO;
    [[[ref queryOrderedByKey] queryStartingAtValue:@"c"] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
        NSMutableArray *keys = [[NSMutableArray alloc] init];
        for (FIRDataSnapshot *child in snapshot.children) {
            [keys addObject:child.key];
        }
        NSArray *expected = @[@"c", @"d", @"e", @"f"];
        XCTAssertEqualObjects(keys, expected, @"");
        valueDone = YES;
    }];
    WAIT_FOR(valueDone);

    NSMutableArray *keys = [[NSMutableArray alloc] init];
    [[[ref queryOrderedByKey] queryLimitedToLast:5] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
        for (FIRDataSnapshot *child in snapshot.children) {
            [keys addObject:child.key];
        }
    }];

    WAIT_FOR(keys.count == 5);
    NSArray *expected = @[@"b", @"c", @"d", @"e", @"f"];
    XCTAssertEqualObjects(keys, expected, @"");
}

- (void) testQueriesWorkOnLeafNodes {
    FIRDatabaseReference *ref = [FTestHelpers getRandomNode];

    [self waitForCompletionOf:ref setValue:@"leaf-node"];

    __block BOOL valueDone = NO;
    [[[ref queryOrderedByChild:@"foo"] queryLimitedToLast:1] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
        XCTAssertEqual(snapshot.value, [NSNull null]);
        valueDone = YES;
    }];
    WAIT_FOR(valueDone);
}

- (void) testUpdatesForUnindexedQuery {
    FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
    FIRDatabaseReference *reader = refs.one;
    FIRDatabaseReference *writer = refs.two;

    __block BOOL done = NO;
    NSDictionary *value = @{ @"one": @{ @"index": @1, @"value": @"one" },
                             @"two": @{ @"index": @2, @"value": @"two" },
                             @"three": @{ @"index": @3, @"value": @"three" } };
    [writer setValue:value withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
        done = YES;
    }];
    WAIT_FOR(done);

    done = NO;

    NSMutableArray *snapshots = [NSMutableArray array];

    [[[reader queryOrderedByChild:@"index"] queryLimitedToLast:2] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
        [snapshots addObject:snapshot.value];
        done = YES;
    }];

    WAIT_FOR(done);

    NSDictionary *expected = @{ @"two": @{ @"index": @2, @"value": @"two" },
                                @"three": @{ @"index": @3, @"value": @"three" } };

    XCTAssertEqual(snapshots.count, (NSUInteger)1);
    XCTAssertEqualObjects(snapshots[0], expected);

    done = NO;
    [[writer child:@"one/index"] setValue:@4];

    WAIT_FOR(done);

    expected = @{ @"one": @{ @"index": @4, @"value": @"one" },
                  @"three": @{ @"index": @3, @"value": @"three" } };
    XCTAssertEqual(snapshots.count, (NSUInteger)2);
    XCTAssertEqualObjects(snapshots[1], expected);
}

- (void) testServerRespectsKeyIndex {
    FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
    FIRDatabaseReference *writer = refs.one;
    FIRDatabaseReference *reader = refs.two;

    NSDictionary *initial = @{
                              @"a": @1,
                              @"b": @2,
                              @"c": @3
                              };

    // If the server doesn't respect the index, it will send down limited data, but with no offset, so the expected
    // and actual data don't match
    FIRDatabaseQuery *query = [[[reader queryOrderedByKey] queryStartingAtValue:@"b"] queryLimitedToFirst:2];

    NSArray *expectedChildren = @[@"b", @"c"];

    [self waitForCompletionOf:writer setValue:initial];

    NSMutableArray *children = [[NSMutableArray alloc] init];

    __block BOOL done = NO;
    [query observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
        for (FIRDataSnapshot *child in snapshot.children) {
            [children addObject:child.key];
        }
        done = YES;
    }];

    WAIT_FOR(done);

    XCTAssertEqualObjects(expectedChildren, children, @"Got correct children");
}

- (void) testServerRespectsValueIndex {
    FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
    FIRDatabaseReference *writer = refs.one;
    FIRDatabaseReference *reader = refs.two;

    NSDictionary *initial = @{
                              @"a": @1,
                              @"c": @2,
                              @"b": @3
                              };

    // If the server doesn't respect the index, it will send down limited data, but with no offset, so the expected
    // and actual data don't match
    FIRDatabaseQuery *query = [[[reader queryOrderedByValue] queryStartingAtValue:@2] queryLimitedToFirst:2];

    NSArray *expectedChildren = @[@"c", @"b"];

    [self waitForCompletionOf:writer setValue:initial];

    NSMutableArray *children = [[NSMutableArray alloc] init];

    __block BOOL done = NO;
    [query observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
        for (FIRDataSnapshot *child in snapshot.children) {
            [children addObject:child.key];
        }
        done = YES;
    }];

    WAIT_FOR(done);

    XCTAssertEqualObjects(expectedChildren, children, @"Got correct children");
}

- (void) testDeepUpdatesWorkWithQueries {
    FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
    FIRDatabaseReference *writer = refs.one;
    FIRDatabaseReference *reader = refs.two;


    NSDictionary *initial = @{@"a": @{@"data": @"foo",
                                      @"idx": @YES},
                              @"b": @{@"data": @"bar",
                                      @"idx": @YES},
                              @"c": @{@"data": @"baz",
                                      @"idx": @NO}};
    [self waitForCompletionOf:writer setValue:initial];

    FIRDatabaseQuery *query = [[reader queryOrderedByChild:@"idx"] queryEqualToValue:@YES];

    NSDictionary* expected = @{@"a": @{@"data": @"foo",
                                       @"idx": @YES},
                               @"b": @{@"data": @"bar",
                                       @"idx": @YES}};

    [self waitForExportValueOf:query toBe:expected];

    NSDictionary *update = @{@"a/idx": @NO,
                             @"b/data": @"blah",
                             @"c/idx": @YES};
    [self waitForCompletionOf:writer updateChildValues:update];

    expected = @{@"b": @{@"data": @"blah",
                         @"idx": @YES},
                 @"c": @{@"data": @"baz",
                         @"idx": @YES}};
   [self waitForExportValueOf:query toBe:expected];
}

- (void) testServerRespectsDeepIndex {
    FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
    FIRDatabaseReference *writer = refs.one;
    FIRDatabaseReference *reader = refs.two;


    NSDictionary *initial = @{
                              @"a": @{@"deep":@{@"index":@1}},
                              @"c": @{@"deep":@{@"index":@2}},
                              @"b": @{@"deep":@{@"index":@3}}
                              };

    // If the server doesn't respect the index, it will send down limited data, but with no offset, so the expected
    // and actual data don't match
    FIRDatabaseQuery *query = [[[reader queryOrderedByChild:@"deep/index"] queryStartingAtValue:@2] queryLimitedToFirst:2];

    NSArray *expectedChildren = @[@"c", @"b"];

    [self waitForCompletionOf:writer setValue:initial];

    NSMutableArray *children = [[NSMutableArray alloc] init];

    __block BOOL done = NO;
    [query observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
        for (FIRDataSnapshot *child in snapshot.children) {
            [children addObject:child.key];
        }
        done = YES;
    }];

    WAIT_FOR(done);

    XCTAssertEqualObjects(expectedChildren, children, @"Got correct children");
}

- (void) testStartAtEndAtWorksWithValueIndex {
    FIRDatabaseReference *ref = [FTestHelpers getRandomNode];

    NSDictionary *initial = @{
                              @"alex": @60,
                              @"rob": @56,
                              @"vassili": @55.5,
                              @"tony": @52,
                              @"greg": @52
                              };

    NSArray *expectedOrder = @[@"tony", @"vassili", @"rob"];
    NSArray *expectedPrevNames = @[[NSNull null], @"tony", @"vassili"];

    NSMutableArray *valueOrder = [[NSMutableArray alloc] init];
    NSMutableArray *addedOrder = [[NSMutableArray alloc] init];
    NSMutableArray *addedPrevNames = [[NSMutableArray alloc] init];

    FIRDatabaseQuery *orderedRef = [[[ref queryOrderedByValue] queryStartingAtValue:@52 childKey:@"tony"] queryEndingAtValue:@59];

    [orderedRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
        for (FIRDataSnapshot *child in snapshot.children) {
            [valueOrder addObject:child.key];
        }
    }];

    [orderedRef observeEventType:FIRDataEventTypeChildAdded andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *prevName) {
        [addedOrder addObject:snapshot.key];
        [addedPrevNames addObject:prevName ? prevName : [NSNull null]];
    }];

    [ref setValue:initial];
    WAIT_FOR(addedOrder.count == expectedOrder.count && valueOrder.count == expectedOrder.count);

    XCTAssertEqualObjects(addedOrder, expectedOrder, @"child_added events in correct order.");
    XCTAssertEqualObjects(addedPrevNames, expectedPrevNames, @"Got correct prevnames for child_added events.");
    XCTAssertEqualObjects(valueOrder, expectedOrder, @"enumerated snapshot children in correct order.");
}

- (void) testRemovingDefaultListenerRemovesNonDefaultListenWithLoadsAllData {
    FIRDatabaseReference *ref = [FTestHelpers getRandomNode];

    NSDictionary *initialData = @{ @"key": @"value" };
    [self waitForCompletionOf:ref setValue:initialData];

    [[ref queryOrderedByKey] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    }];
    [ref observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    }];

    // Should remove both listener and should remove the listen sent to the server
    [ref removeAllObservers];

    __block id result = nil;
    // This used to crash because a listener for [ref queryOrderedByKey] existed already
    [[ref queryOrderedByKey] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
        result = snapshot.value;
    }];

    WAIT_FOR(result);
    XCTAssertEqualObjects(result, initialData);
}

@end
